/**
  * Megasquirt Logger for PalmOS
  * Copyright (C) 2011 Andrew Beard
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  
#include <PalmOS.h>
#include <PalmOSGlue.h>
#include <DataMgr.h>
#include <StdIOPalm.h>

/* MathLib headers */
#include "MathLib.h"

#include "MegasquirtLogger.h"
#include "MegasquirtLogger_Rsc.h"
#include "MegasquirtInterface.h"
#include "LogFile.h"

/*********************************************************************
 * Entry Points
 *********************************************************************/

/*********************************************************************
 * Global variables
 *********************************************************************/
/* 
 * g_prefs
 * cache for application preferences during program execution 
 */
MegasquirtLoggerPreferenceType g_prefs;
MegasquirtInterface*           g_pMegasquirtInterface = NULL;
LogFile*                       g_pLogFile             = NULL;
UInt16                         g_nSerialPortID        = 0;
UInt32                         g_nNumUpdateRecords    = 0;

/*********************************************************************
 * Internal Constants
 *********************************************************************/

/* Define the minimum OS version we support */
#define ourMinVersion    sysMakeROMVersion(3,0,0,sysROMStageDevelopment,0)
#define kPalmOS20Version sysMakeROMVersion(2,0,0,sysROMStageDevelopment,0)

/*********************************************************************
 * Internal Functions
 *********************************************************************/

static void PopulateMainFormVersion( void )
{
    MemHandle  hOldTextHandle;
    MemHandle  hNewTextHandle;
    MemHandle  hVersionStringHandle;
    FormPtr    currentForm;
    Char*      pVersionString;
    Char*      pFieldString;
    
    FieldType* field;
    UInt16     fieldIndex;
    
    currentForm = FrmGetActiveForm();
             
    // Allocate a handle for our new field text
    hNewTextHandle = MemHandleNew( 20 );
    pFieldString = (Char*)MemHandleLock( hNewTextHandle );
    
    // Lock the version string resource
	hVersionStringHandle = DmGetResource( 'tSTR',
	                                      AppVersion );
	                                      
	// Build the string that goes on the form
	StrCopy( pFieldString,
             "Version " );
	pVersionString = (Char*)MemHandleLock( hVersionStringHandle );
	StrCat( pFieldString,
	        pVersionString );
	        
	MemHandleUnlock( hNewTextHandle );
	
    // Let go of the version string resource
	MemHandleUnlock( hVersionStringHandle );
	DmReleaseResource( hVersionStringHandle );
	
	// Deal with the Version field
	fieldIndex = FrmGetObjectIndex( currentForm,
	                                MainFieldVersion );
	field = (FieldType *)FrmGetObjectPtr( currentForm,
	                                      fieldIndex );
    hOldTextHandle = FldGetTextHandle( field );
    
    // Set the new text
    FldSetTextHandle( field,
                      hNewTextHandle );
    FldDrawField( field );
	
	
	if ( hOldTextHandle )
	{
	    MemHandleFree( hOldTextHandle );
	}
}

static void PopulateSignatureField( UInt16 nFieldIdentifier )
{
    MemHandle   hOldTextHandle;
    MemHandle   hNewTextHandle;
    FormPtr     currentForm;
    const Char* pSignatureString;
    Char*       pFieldString;
    
    FieldType* field;
    UInt16     fieldIndex;
    
    currentForm = FrmGetActiveForm();
             
    // Get the signature string
    pSignatureString = g_pMegasquirtInterface->GetSignature();
             
    // Allocate a handle for our new field text
    hNewTextHandle = MemHandleNew( StrLen(pSignatureString) + 1 );
    pFieldString = (Char*)MemHandleLock( hNewTextHandle );
	                                      
	// Copy the string that goes on the form
	StrCopy( pFieldString,
             pSignatureString );
	MemHandleUnlock( hNewTextHandle );
	
	// Deal with the Version field
	fieldIndex = FrmGetObjectIndex( currentForm,
	                                nFieldIdentifier );
	field = (FieldType *)FrmGetObjectPtr( currentForm,
	                                      fieldIndex );
    hOldTextHandle = FldGetTextHandle( field );
    
    // Set the new text
    FldSetTextHandle( field,
                      hNewTextHandle );
    FldDrawField( field );
	
	
	if ( hOldTextHandle )
	{
	    MemHandleFree( hOldTextHandle );
	}
}

static void PopulateLoggingNumRecordsField( UInt16 nNumRecords )
{
    MemHandle   hOldTextHandle;
    MemHandle   hNewTextHandle;
    FormPtr     currentForm;
	Char*       pFieldString;
    
    FieldType* field;
    UInt16     fieldIndex;
    
    currentForm = FrmGetActiveForm();
             
    // Allocate a handle for our new field text
    hNewTextHandle = MemHandleNew( maxStrIToALen );
    pFieldString = (Char*)MemHandleLock( hNewTextHandle );
	               
    // Format the number of records into a string
	StrIToA( pFieldString,
	         nNumRecords );				   
	MemHandleUnlock( hNewTextHandle );
	
	// Deal with the Version field
	fieldIndex = FrmGetObjectIndex( currentForm,
	                                LoggingFieldNumRecords );
	field = (FieldType *)FrmGetObjectPtr( currentForm,
	                                      fieldIndex );
    hOldTextHandle = FldGetTextHandle( field );
    
    // Set the new text
    FldSetTextHandle( field,
                      hNewTextHandle );
    FldDrawField( field );
	
	if ( hOldTextHandle )
	{
	    MemHandleFree( hOldTextHandle );
	}
}

static void ShowVFS_ErrorAlert( Err errorCode )
{
    Char        bufErrorCodeString[ 10 ];
    const Char* pErrorString = "\0";
    
    switch ( errorCode )
    {
        case vfsErrFileBadRef:
        {
            pErrorString = "Internal file handling error";
            break;
        }
        case vfsErrVolumeBadRef:
        {
            pErrorString = "Memory card not found";
            break;
        }
        default:
        {
            pErrorString = "Unknown VFS error";
            break;
        }
    }
    
    sprintf( bufErrorCodeString,
             "0x%X",
             errorCode );
             
    FrmCustomAlert( AlertVFS_Error,
                    pErrorString,
                    bufErrorCodeString,
                    NULL );
}

/*
 * FUNCTION: MainFormInit
 *
 * DESCRIPTION: This routine initializes the MainForm form.
 *
 * PARAMETERS:
 *
 * frm
 *     pointer to the MainForm form.
 */

static void MainFormInit(FormType *frmP)
{
    PopulateMainFormVersion();
}

/*
 * FUNCTION: ConnectedFormInit
 *
 * DESCRIPTION: This routine initializes the ConnectedForm form.
 *
 * PARAMETERS:
 *
 * frm
 *     pointer to the ConnectedForm form.
 */

static void ConnectedFormInit( FormType* frmP )
{
    if ( g_pMegasquirtInterface )
    {
        if ( g_pMegasquirtInterface->ReceivedSignature() )
        {
            PopulateSignatureField( ConnectedFieldSignature );
        }
    }
}

/*
 * FUNCTION: LoggingFormInit
 *
 * DESCRIPTION: This routine initializes the LoggingForm form.
 *
 * PARAMETERS:
 *
 * frm
 *     pointer to the LoggingForm form.
 */

static void LoggingFormInit( FormType* frmP )
{
    PopulateLoggingNumRecordsField( 0 );
    PopulateSignatureField( LoggingFieldSignature );
}

/*
 * FUNCTION: MainFormDoCommand
 *
 * DESCRIPTION: This routine performs the menu command specified.
 *
 * PARAMETERS:
 *
 * command
 *     menu item id
 */

static Boolean MainFormDoCommand(UInt16 command)
{
	Boolean handled = false;

	switch (command)
	{
		case OptionsPreferences:
		{
			FormType * frmP;
			ControlType * cbox = NULL;
			FieldType * field = NULL;			
			UInt16 controlID = 0;
			MemHandle handle = 0;

			/* Clear the menu status from the display */
			MenuEraseStatus(0);

			/* Initialize the preference form. */
			frmP = FrmInitForm (PrefsForm);

			/* 
			 * Set the controls in the preference dialog to reflect our 
			 * preference data structure
			 */
			field = (FieldType *)FrmGetObjectPtr(frmP, FrmGetObjectIndex(frmP, PerfsFieldRTU_Size));
			if ( field && (g_prefs.nRTU_Size != 0) )
			{
				MemPtr text = NULL;
				Char   formattedNumber[ maxStrIToALen ];
				StrIToA( formattedNumber,
				         g_prefs.nRTU_Size );
				
				UInt32 len = StrLen( formattedNumber );
				
				handle = FldGetTextHandle(field);
				if (!handle)
					handle = MemHandleNew(len + 1);
				else
					MemHandleResize(handle, len + 1);

				text = MemHandleLock(handle);
				if (text)
					StrCopy( (char *)text,
					         formattedNumber );

				MemHandleUnlock(handle);
				FldSetTextHandle(field, handle);
				
				handle = 0;
			}

			/* 
			 * Display the preferences dialog. The call will block until 
			 * the dialog is dismissed
			 */
			controlID = FrmDoDialog (frmP);
			
			/* controlID contains the ID of the button used to dismiss the dialog */
			if (controlID == PrefsOKButton)
			{
				/* 
				 * The user hit the OK button. Get the value of the controls 
				 * and store them in our pref struct 
				 */
				if (field)
				{
					handle = FldGetTextHandle(field);
					if (handle)
					{				
						MemPtr text = MemHandleLock(handle);
						if (text)
						{
							/* Guard against the field text being longer than our pref's buffer */
                            g_prefs.nRTU_Size = StrAToI( (const Char*)text );
						}

						MemHandleUnlock(handle);
					}
				}
			}
			
			/* Clean up */
			FrmDeleteForm (frmP);

			handled = true;
			break;
		}

	}

	return handled;
}

/*
 * FUNCTION: MainFormHandleEvent
 *
 * DESCRIPTION:
 *
 * This routine is the event handler for the "MainForm" of this 
 * application.
 *
 * PARAMETERS:
 *
 * eventP
 *     a pointer to an EventType structure
 *
 * RETURNED:
 *     true if the event was handled and should not be passed to
 *     FrmHandleEvent
 */

static Boolean MainFormHandleEvent(EventType * eventP)
{
	Boolean handled = false;
	FormType * frmP;

	switch (eventP->eType) 
	{
		case menuEvent:
			return MainFormDoCommand(eventP->data.menu.itemID);

		case frmOpenEvent:
			frmP = FrmGetActiveForm();
			FrmDrawForm(frmP);
			MainFormInit(frmP);
			handled = true;
			break;
            
        case frmUpdateEvent:
			/* 
			 * To do any custom drawing here, first call
			 * FrmDrawForm(), then do your drawing, and
			 * then set handled to true. 
			 */
			break;
			
		case ctlSelectEvent:
		{
			if ( eventP->data.ctlSelect.controlID == MainConnectButton )
			{
				/* "Connect" button was hit */
				if ( !g_pMegasquirtInterface )
				{
				    Err tSerialOpenError = errNone;
				    
				    tSerialOpenError = SrmOpen( serPortCradleRS232Port,
				                                115200,
				                                &g_nSerialPortID );
				
				    g_pMegasquirtInterface = new MegasquirtInterface( g_nSerialPortID,
				                                                      g_prefs.nRTU_Size );
				    g_pMegasquirtInterface->RequestSignature();
				}
				
				FrmGotoForm( ConnectedForm );
				break;
			}

			break;
		}
	}
    
	return handled;
}

/*
 * FUNCTION: ConnectedFormHandleEvent
 *
 * DESCRIPTION:
 *
 * This routine is the event handler for the "ConnectedForm" of this 
 * application.
 *
 * PARAMETERS:
 *
 * eventP
 *     a pointer to an EventType structure
 *
 * RETURNED:
 *     true if the event was handled and should not be passed to
 *     FrmHandleEvent
 */

static Boolean ConnectedFormHandleEvent( EventType* eventP )
{
	Boolean handled = false;
	FormType * frmP;

	switch ( eventP->eType ) 
	{
		case frmOpenEvent:
			frmP = FrmGetActiveForm();
			FrmDrawForm(frmP);
			ConnectedFormInit(frmP);
			handled = true;
			break;
            
        case frmUpdateEvent:
			/* 
			 * To do any custom drawing here, first call
			 * FrmDrawForm(), then do your drawing, and
			 * then set handled to true. 
			 */
			break;
			
		case ctlSelectEvent:
		{
			if      ( eventP->data.ctlSelect.controlID == ConnectedButtonStartLogging )
			{
				/* "Start Logging" button was hit */
				if ( g_pMegasquirtInterface->ReceivedSignature() )
				{
				    bool bLogFileOK = false;
				    g_nNumUpdateRecords = 0;
				    
				    if ( !g_pLogFile )
				    {
                        UInt32       nTimeInSeconds = 0;
                        DateTimeType nCurrentDateTime;

                        nTimeInSeconds = TimGetSeconds();
                        TimSecondsToDateTime( nTimeInSeconds,
                                              &nCurrentDateTime );
				    
				        g_pLogFile = new LogFile( nCurrentDateTime );
				        if ( g_pLogFile->GetLastError() == errNone )
				        {
    				        if ( g_pLogFile->IsFileOpen() )
    				        {
    				            bLogFileOK = true;
    				            g_pLogFile->WriteHeader( nCurrentDateTime,
    				                                     g_pMegasquirtInterface->GetSignature(),
    				                                     g_prefs.nRTU_Size );
    				        }
    				        else
    				        {
    				            // How did this happen?
    				        }
				        }
				        else
				        {
				            ShowVFS_ErrorAlert( g_pLogFile->GetLastError() );
				        }
				    }
				    else
				    {
				        bLogFileOK = true;
				    }
				    
				    if ( bLogFileOK )
				    {
				    	FrmGotoForm( LoggingForm );
				        g_pMegasquirtInterface->RequestRealtimeUpdate();
				    }
			    }
				else
				{
				    FrmAlert( NoSignatureAlert );
	            }
				break;
			}
			else if ( eventP->data.ctlSelect.controlID == ConnectedButtonDisconnect )
			{
				/* "Disconnect" button was hit */
				if ( g_pMegasquirtInterface )
				{
				    Err serialCloseError = errNone;
				
				    delete g_pMegasquirtInterface;
				    g_pMegasquirtInterface = NULL;
				    
				    serialCloseError = SrmClose( g_nSerialPortID );
				    g_nSerialPortID = 0;
				}
				FrmGotoForm( MainForm );
				break;
			}

			break;
		}
		case nilEvent:
		{
		    if ( g_pMegasquirtInterface )
		    {
		        g_pMegasquirtInterface->ProcessRxData();
		        
		        if ( g_pMegasquirtInterface->CheckSignatureEvent() )
		        {
		            PopulateSignatureField( ConnectedFieldSignature );
		        }
		    }
		    break;
		}
	}
    
	return handled;
}

/*
 * FUNCTION: LoggingFormHandleEvent
 *
 * DESCRIPTION:
 *
 * This routine is the event handler for the "LoggingForm" of this 
 * application.
 *
 * PARAMETERS:
 *
 * eventP
 *     a pointer to an EventType structure
 *
 * RETURNED:
 *     true if the event was handled and should not be passed to
 *     FrmHandleEvent
 */

static Boolean LoggingFormHandleEvent( EventType* eventP )
{
	Boolean handled = false;
	FormType * frmP;

	switch (eventP->eType) 
	{
		case frmOpenEvent:
			frmP = FrmGetActiveForm();
			FrmDrawForm(frmP);
			LoggingFormInit(frmP);
			handled = true;
			break;
            
        case frmUpdateEvent:
			/* 
			 * To do any custom drawing here, first call
			 * FrmDrawForm(), then do your drawing, and
			 * then set handled to true. 
			 */
			break;
			
		case ctlSelectEvent:
		{
			if ( eventP->data.ctlSelect.controlID == LoggingButtonStop )
			{
				/* "Stop" button was hit */
				if ( g_pLogFile )
				{
				    delete g_pLogFile;
				    g_pLogFile = NULL;
				}
				
				FrmGotoForm( ConnectedForm );
				break;
			}

			break;
		}
		
		case nilEvent:
		{
		    if ( g_pMegasquirtInterface )
		    {
		        g_pMegasquirtInterface->ProcessRxData();
		        
		        if ( g_pMegasquirtInterface->CheckRealtimeUpdateEvent() )
		        {
                    g_pLogFile->WriteOutputRecord( g_pMegasquirtInterface->GetRealtimeUpdateData()  );
                    if ( g_pLogFile->GetLastError() != errNone )
                    {
                        ShowVFS_ErrorAlert( g_pLogFile->GetLastError() );
                    }
					g_nNumUpdateRecords++;	
					g_pMegasquirtInterface->RequestRealtimeUpdate();
					PopulateLoggingNumRecordsField( g_nNumUpdateRecords );
		        }
		    }
		    break;
		}
	}
    
	return handled;
}

/*
 * FUNCTION: AppHandleEvent
 *
 * DESCRIPTION: 
 *
 * This routine loads form resources and set the event handler for
 * the form loaded.
 *
 * PARAMETERS:
 *
 * event
 *     a pointer to an EventType structure
 *
 * RETURNED:
 *     true if the event was handled and should not be passed
 *     to a higher level handler.
 */

static Boolean AppHandleEvent(EventType * eventP)
{
	UInt16 formId;
	FormType * frmP;

	if (eventP->eType == frmLoadEvent)
	{
		/* Load the form resource. */
		formId = eventP->data.frmLoad.formID;
		frmP = FrmInitForm(formId);
		FrmSetActiveForm(frmP);

		/* 
		 * Set the event handler for the form.  The handler of the
		 * currently active form is called by FrmHandleEvent each
		 * time is receives an event. 
		 */
		switch (formId)
		{
			case MainForm:
			{
				FrmSetEventHandler( frmP,
				                    MainFormHandleEvent );
				break;
		    }
			case ConnectedForm:
			{
				FrmSetEventHandler( frmP,
				                    ConnectedFormHandleEvent );
				break;
		    }
		    case LoggingForm:
			{
				FrmSetEventHandler( frmP,
				                    LoggingFormHandleEvent );
				break;
		    }
		}
		return true;
	}

	return false;
}

/*
 * FUNCTION: AppEventLoop
 *
 * DESCRIPTION: This routine is the event loop for the application.
 */

static void AppEventLoop(void)
{
	UInt16 error;
	EventType event;

	do 
	{
		/* change timeout if you need periodic nilEvents */
		EvtGetEvent(&event, evtWaitForever);

		if (! SysHandleEvent(&event))
		{
			if (! MenuHandleEvent(0, &event, &error))
			{
				if (! AppHandleEvent(&event))
				{
					FrmDispatchEvent(&event);
				}
			}
		}
	} while (event.eType != appStopEvent);
}

/*
 * FUNCTION: AppStart
 *
 * DESCRIPTION:  Get the current application's preferences.
 *
 * RETURNED:
 *     errNone - if nothing went wrong
 */

static Err AppStart(void)
{
	UInt16 prefsSize;

	/* load the MathLib library, save reference in MathLibRef */
	{
		MathLibRef = sysInvalidRefNum;

		if (errNone != SysLibFind(MathLibName, &MathLibRef))
		{
			SysLibLoad(LibType, MathLibCreator, &MathLibRef);
		}
		
		if (MathLibRef != sysInvalidRefNum)
		{
			if (mlErrNone != MathLibOpen(MathLibRef, MathLibVersion))
			{
				/* if error loading library, reset reference */
				MathLibRef = sysInvalidRefNum;
			}
		}
	}
	/* Read the saved preferences / saved-state information. */
	prefsSize = sizeof(g_prefs);
	if (PrefGetAppPreferences(
		appFileCreator, appPrefID, &g_prefs, &prefsSize, true) == 
		noPreferenceFound)
	{
		/* no prefs; initialize pref struct with default values */
		g_prefs.nRTU_Size = 169;
	}

	return errNone;
}

/*
 * FUNCTION: AppStop
 *
 * DESCRIPTION: Save the current state of the application.
 */

static void AppStop(void)
{
	/* 
	 * Write the saved preferences / saved-state information.  This
	 * data will be saved during a HotSync backup. 
	 */
	PrefSetAppPreferences(
		appFileCreator, appPrefID, appPrefVersionNum, 
		&g_prefs, sizeof(g_prefs), true);
        
    /* Close the Megasquirt Interface and serial port if needed */
    if ( g_pMegasquirtInterface )
	{
	    Err serialCloseError = errNone;
	
	    delete g_pMegasquirtInterface;
	    g_pMegasquirtInterface = NULL;
	    
	    serialCloseError = SrmClose( g_nSerialPortID );
	    g_nSerialPortID = 0;
	}
	
	/* Close out the log file if needed */
	if ( g_pLogFile )
	{
	    delete g_pLogFile;
	    g_pLogFile = NULL;
	}
        
	/* Close all the open forms. */
	FrmCloseAllForms();

	/* unload the MathLib library */
	if (MathLibRef != sysInvalidRefNum)
	{
		UInt16 useCount;

		MathLibClose(MathLibRef, &useCount);
		if (useCount == 0)
		{
			SysLibRemove(MathLibRef);
		}

		MathLibRef = sysInvalidRefNum;
	}

}

/*
 * FUNCTION: RomVersionCompatible
 *
 * DESCRIPTION: 
 *
 * This routine checks that a ROM version is meet your minimum 
 * requirement.
 *
 * PARAMETERS:
 *
 * requiredVersion
 *     minimum rom version required
 *     (see sysFtrNumROMVersion in SystemMgr.h for format)
 *
 * launchFlags
 *     flags that indicate if the application UI is initialized
 *     These flags are one of the parameters to your app's PilotMain
 *
 * RETURNED:
 *     error code or zero if ROM version is compatible
 */

static Err RomVersionCompatible(UInt32 requiredVersion, UInt16 launchFlags)
{
	UInt32 romVersion;

	/* See if we're on in minimum required version of the ROM or later. */
	FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
	if (romVersion < requiredVersion)
	{
		if ((launchFlags & 
			(sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp)) ==
			(sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp))
		{
			FrmAlert (RomIncompatibleAlert);

			/* Palm OS versions before 2.0 will continuously relaunch this
			 * app unless we switch to another safe one. */
			if (romVersion < kPalmOS20Version)
			{
				AppLaunchWithCommand(
					sysFileCDefaultApp, 
					sysAppLaunchCmdNormalLaunch, NULL);
			}
		}

		return sysErrRomIncompatible;
	}

	return errNone;
}

/*
 * FUNCTION: PilotMain
 *
 * DESCRIPTION: This is the main entry point for the application.
 * 
 * PARAMETERS:
 *
 * cmd
 *     word value specifying the launch code. 
 *
 * cmdPB
 *     pointer to a structure that is associated with the launch code
 *
 * launchFlags
 *     word value providing extra information about the launch.
 *
 * RETURNED:
 *     Result of launch, errNone if all went OK
 */

UInt32 PilotMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
	Err error;

	error = RomVersionCompatible (ourMinVersion, launchFlags);
	if (error) return (error);

	switch (cmd)
	{
		case sysAppLaunchCmdNormalLaunch:
			error = AppStart();
			if (error) 
				return error;

			/* 
			 * start application by opening the main form
			 * and then entering the main event loop 
			 */
			FrmGotoForm(MainForm);
			AppEventLoop();

			AppStop();
			break;
	}

	return errNone;
}
